package utils

import (
	"archive/tar"
	"archive/zip"
	"compress/gzip"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
)

const (
	ErrPathNotAbsMSG   = "not an absolute path, path=%s"
	ErrPathNotExistMSG = "no such file or directory, path=%s"
	ErrPathEmptyMSG    = "it's an empty file, path=%s"

	ErrPathStatMSG   = "failed to check the status of the path, path=%s, err=%w"
	ErrPathOpenMSG   = "fail to open file or directory, path=%s, err=%w"
	ErrPathReadMSG   = "fail to read file or directory, path=%s, err=%w"
	ErrPathWriteMSG  = "fail to write file or directory, path=%s, err=%w"
	ErrPathDeleteMSG = "fail to delete file or directory, path=%s, err=%w"
	ErrPathCreateMSG = "fail to touch file or make directory, path=%s, err=%w"
	ErrPathCopyMSG   = "fail to copy file or directory, src_path=%s, dst_path=%s, err=%w"

	ErrFileTypeMSG     = "file is not a %s file, path=%s"
	ErrPathRelativeMsg = "failed to obtain relative path, basepath=%s, targpath=%s, err=%w"
)

// MakrPathDir 确保路径所对应的目录存在。
// 如果目录不存在，函数将创建目录。
// 参数:
//
//	dirPath - 目录的绝对路径。
//
// 返回值:
//
//	error - 如果创建文件夹的过程发生任何错误，则返回该错误。
func MakrPathDir(dirPath string, isClear bool) error {
	if !filepath.IsAbs(dirPath) {
		return fmt.Errorf(ErrPathNotAbsMSG, dirPath)
	}

	dirInfo, errStat := os.Stat(dirPath)
	if os.IsNotExist(errStat) {
		errMkdir := os.MkdirAll(dirPath, 0755)
		if errMkdir != nil {
			return fmt.Errorf(ErrPathCreateMSG, dirPath, errMkdir)
		}
	} else if errStat != nil {
		return fmt.Errorf(ErrPathStatMSG, dirPath, errStat)
	} else if !dirInfo.IsDir() {
		return fmt.Errorf(ErrPathNotExistMSG, dirPath)
	} else if isClear {
		rmErr := os.RemoveAll(dirPath)
		if rmErr != nil {
			return fmt.Errorf(ErrPathDeleteMSG, dirPath, rmErr)
		}
		errMkdir := os.MkdirAll(dirPath, 0755)
		if errMkdir != nil {
			return fmt.Errorf(ErrPathCreateMSG, dirPath, errMkdir)
		}
	}
	return nil
}

// copyFile 复制文件从源路径到目标路径。
// 参数:
//
//	srcFile - 源文件的路径，必须是绝对路径。
//	dstFile - 目标文件的路径，必须是绝对路径。
//
// 返回值:
//
//	error - 如果拷贝文件的过程发生任何错误，则返回该错误。
func copyFile(srcFile string, dstFile string) error {
	srcInfo, srcErr := os.Open(srcFile)
	if srcErr != nil {
		return fmt.Errorf(ErrPathReadMSG, srcFile, srcErr)
	}
	defer srcInfo.Close()

	srcStat, statErr := srcInfo.Stat()
	if statErr != nil {
		return statErr
	}
	if srcStat.Size() == 0 {
		return fmt.Errorf(ErrPathEmptyMSG, srcFile)
	}
	dstInfo, dstErr := os.OpenFile(dstFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, srcStat.Mode())
	if dstErr != nil {
		return fmt.Errorf(ErrPathCreateMSG, dstFile, dstErr)
	}
	defer dstInfo.Close()

	_, copyErr := io.Copy(dstInfo, srcInfo)
	if copyErr != nil {
		return fmt.Errorf(ErrPathCopyMSG, srcFile, dstFile, copyErr)
	}
	return nil
}

// copyDir 复制源路径下的所有文件和子目录到目标路径。
// 参数:
//
//	srcFile - 源文件的路径，必须是绝对路径。
//	dstFile - 目标文件的路径，必须是绝对路径。
//
// 返回值:
//
//	error - 如果拷贝文件夹的过程发生任何错误，则返回该错误。
func copyDir(srcDir string, dstDir string) error {
	mkdirErr := MakrPathDir(dstDir, false)
	if mkdirErr != nil {
		return mkdirErr
	}
	entries, readErr := os.ReadDir(srcDir)
	if readErr != nil {
		return fmt.Errorf(ErrPathReadMSG, srcDir, readErr)
	}

	for _, entry := range entries {
		srcPath := filepath.Join(srcDir, entry.Name())
		dstPath := filepath.Join(dstDir, entry.Name())
		if entry.IsDir() {
			copyErr := copyDir(srcPath, dstPath)
			if copyErr != nil {
				return copyErr
			}
		} else {
			copyErr := copyFile(srcPath, dstPath)
			if copyErr != nil {
				return copyErr
			}
		}
	}
	return nil
}

// CopyPath 复制指定的文件或目录从一个位置到另一个位置。
// 该函数首先检查源路径（srcPath）和目标路径（dstPath）是否为绝对路径，
// 如果不是，则返回错误。接着，它会检查源路径是指向一个文件还是一个目录，
// 然后调用相应的函数（copyFile或copyDir）来执行实际的复制操作。
// 参数:
//
//	srcPath - 源文件或目录的路径，必须是绝对路径。
//	dstPath - 目标文件或目录的路径，必须是绝对路径。
//
// 返回值:
//
//	error - 如果拷贝文件或文件夹的过程发生任何错误，则返回该错误。
func CopyPath(srcPath string, dstPath string) error {
	if !filepath.IsAbs(srcPath) {
		return fmt.Errorf(ErrPathNotAbsMSG, srcPath)
	}
	if !filepath.IsAbs(dstPath) {
		return fmt.Errorf(ErrPathNotAbsMSG, dstPath)
	}
	srcInfo, errStat := os.Stat(srcPath)
	if errStat != nil {
		return errStat
	}
	if srcInfo.IsDir() {
		return copyDir(srcPath, dstPath)
	}
	return copyFile(srcPath, dstPath)
}

// DeletePath 尝试删除给定的文件或目录路径。
// 参数:
//
//	path - 待删除的文件或目录，必须是绝对路径。
//
// 返回值:
//
//	error - 如果删除文件或文件夹的过程发生任何错误，则返回该错误。
func DeletePath(path string) error {
	if !filepath.IsAbs(path) {
		return fmt.Errorf(ErrPathNotAbsMSG, path)
	}
	rmErr := os.RemoveAll(path)
	if rmErr != nil {
		return fmt.Errorf(ErrPathDeleteMSG, path, rmErr)
	}
	return nil
}

// ReadStrFile 读取文件内容并以字符串形式返回。
// 该函数仅处理绝对路径的文件，确保路径的正确性。
// 参数:
//
//	path - 文件的绝对路径。
//
// 返回值:
//
//	string - 文件内容的字符串表示。
//	error - 如果读取文件的过程发生任何错误，则返回该错误。
func ReadStrFile(path string) (string, error) {
	if !filepath.IsAbs(path) {
		return "", fmt.Errorf(ErrPathNotAbsMSG, path)
	}
	data, readErr := os.ReadFile(path)
	if readErr != nil {
		return "", fmt.Errorf(ErrPathReadMSG, path, readErr)
	}
	return string(data), nil
}

// WriteStrFile 将指定的内容写入到给定路径的文件中。
// 如果文件不存在，会自动创建文件。如果路径不是绝对路径，则会返回错误。
// 参数:
//
//	path - 文件的绝对路径。
//	content - 需要写入文件的内容。
//
// 返回值:
//
//	error - 如果将字符串写入文件的过程发生任何错误，则返回该错误。
func WriteStrFile(path string, content string) error {
	if !filepath.IsAbs(path) {
		return fmt.Errorf(ErrPathNotAbsMSG, path)
	}
	file, openFileErr := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
	if openFileErr != nil {
		return fmt.Errorf(ErrPathOpenMSG, path, openFileErr)
	}
	defer file.Close()

	_, writeFileErr := file.WriteString(content)
	if writeFileErr != nil {
		return fmt.Errorf(ErrPathWriteMSG, path, writeFileErr)
	}
	return nil
}

// IsZipFile 检查给定路径的文件是否为ZIP文件。
// 参数:
//
//	path - 文件的路径，应为绝对路径。
//
// 返回值:
//
//	bool - 如果文件是ZIP文件则为true，否则为false。
//	error - 如果检查文件的过程发生任何错误，则返回该错误。
func IsZipFile(path string) (bool, error) {
	if !filepath.IsAbs(path) {
		return false, fmt.Errorf(ErrPathNotAbsMSG, path)
	}
	ext := filepath.Ext(path)
	if ext != ".zip" {
		return false, fmt.Errorf(ErrFileTypeMSG, "zip", path)
	}
	file, openErr := os.Open(path)
	if openErr != nil {
		return false, fmt.Errorf(ErrPathOpenMSG, path, openErr)
	}
	defer file.Close()

	header := make([]byte, 4)
	n, readErr := file.Read(header)
	if readErr != nil {
		if readErr == io.EOF {
			return false, nil
		}
		return false, fmt.Errorf(ErrPathReadMSG, path, readErr)
	}
	if n != 4 {
		return false, nil
	}
	if header[0] == 0x50 && header[1] == 0x4B && header[2] == 0x03 && header[3] == 0x04 {
		return true, nil
	}

	return false, nil
}

// UnzpPath 解压缩指定的zip文件到目标目录。
// 参数:
//
//	srcPath - 源文件或目录的路径，必须是绝对路径。
//	dstPath - 目标目录的路径，必须是绝对路径。
//	isMkdir - 是否在解压前创建目标路径的目录。
//
// 返回值:
//
//	error - 如果解压zip文件的过程发生任何错误，则返回该错误。
func UnzpPath(srcPath string, dstPath string, isMkdir bool) (string, error) {
	if !filepath.IsAbs(srcPath) {
		return "", fmt.Errorf(ErrPathNotAbsMSG, srcPath)
	}
	if !filepath.IsAbs(dstPath) {
		return "", fmt.Errorf(ErrPathNotAbsMSG, dstPath)
	}
	isZip, checkZipErr := IsZipFile(srcPath)
	if checkZipErr != nil {
		return "", checkZipErr
	} else if !isZip {
		return "", fmt.Errorf(ErrFileTypeMSG, "zip", srcPath)
	}
	if isMkdir {
		mkdirErr := MakrPathDir(dstPath, false)
		if mkdirErr != nil {
			return "", mkdirErr
		}
	}

	zipReader, openReaderErr := zip.OpenReader(srcPath)
	if openReaderErr != nil {
		return "", fmt.Errorf(ErrPathOpenMSG, srcPath, openReaderErr)
	}
	defer zipReader.Close()

	for _, file := range zipReader.File {
		if strings.Contains(file.Name, "..") {
			return "", fmt.Errorf("the file contains malicious paths attempting to navigate to the parent directory, path=%s", srcPath)
		}

		fileInZip, fileOpneErr := file.Open()
		if fileOpneErr != nil {
			return "", fmt.Errorf(ErrPathOpenMSG, file.Name, fileOpneErr)
		}
		defer fileInZip.Close()

		dstFilePath := filepath.Join(dstPath, file.Name)
		if file.FileInfo().IsDir() {
			mkdirErr := os.MkdirAll(dstFilePath, file.Mode())
			if mkdirErr != nil {
				return "", fmt.Errorf(ErrPathCreateMSG, dstFilePath, mkdirErr)
			}
		} else {
			dstFile, openFileErr := os.OpenFile(dstFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
			if openFileErr != nil {
				return "", fmt.Errorf(ErrPathCreateMSG, dstFilePath, openFileErr)
			}
			defer dstFile.Close()
			_, cpErr := io.Copy(dstFile, fileInZip)
			if cpErr != nil {
				return "", fmt.Errorf(ErrPathWriteMSG, dstFilePath, cpErr)
			}
		}
	}
	filePath := zipReader.File[0].Name
	return filepath.Join(dstPath, filepath.Dir(filePath)), nil
}

// ZipPath 压缩指定的zip文件到目标路径。
// 参数:
//
//	srcPath - 源文件或目录的路径，必须是绝对路径。
//	dstPath - 目标文件或目录的路径，必须是绝对路径。
//	isMkdir - 是否在解压前创建目标路径的目录。
//
// 返回值:
//
//	error - 如果压缩zip文件的过程发生任何错误，则返回该错误。
func ZipPath(srcPath string, dstPath string) error {
	if !filepath.IsAbs(srcPath) {
		return fmt.Errorf(ErrPathNotAbsMSG, srcPath)
	}
	if !filepath.IsAbs(dstPath) {
		return fmt.Errorf(ErrPathNotAbsMSG, dstPath)
	}
	srcInfo, srcStatErr := os.Stat(srcPath)
	if srcStatErr != nil {
		if os.IsNotExist(srcStatErr) {
			return fmt.Errorf(ErrPathNotExistMSG, srcPath)
		}
		return fmt.Errorf(ErrPathStatMSG, srcPath, srcStatErr)
	}
	ext := filepath.Ext(dstPath)
	if ext != ".zip" {
		return fmt.Errorf(ErrFileTypeMSG, "zip", dstPath)
	}
	dstDir := filepath.Dir(dstPath)
	mkdirErr := MakrPathDir(dstDir, false)
	if mkdirErr != nil {
		return mkdirErr
	}

	zipFile, fileTouchErr := os.Create(dstPath)
	if fileTouchErr != nil {
		return fmt.Errorf(ErrPathCreateMSG, dstPath, fileTouchErr)
	}
	defer zipFile.Close()
	zipWriter := zip.NewWriter(zipFile)
	defer zipWriter.Close()
	if srcInfo.IsDir() {
		return addDirToZip(zipWriter, srcPath, srcPath)
	}
	return addFileToZip(zipWriter, srcPath, filepath.Dir(srcPath))
}

// addDirToZip 是一个递归函数，用于将指定目录及其子目录下的所有文件和子目录添加到 zip 文件中。
// 参数:
//
//	zipWriter (*zip.Writer) - 用于写入 zip 文件的写入器。
//	srcDir (string) - 需要添加到 zip 文件的源目录路径。
//	arcname (string) - 文件在 zip 文件中的存档名称。
//
// 返回值:
//
//	error - 如果操作成功，则返回 nil；否则返回错误。
func addDirToZip(zipWriter *zip.Writer, srcDir string, arcname string) error {
	entries, readRrr := os.ReadDir(srcDir)
	if readRrr != nil {
		return fmt.Errorf(ErrPathReadMSG, srcDir, readRrr)
	}

	for _, entry := range entries {
		srcPath := filepath.Join(srcDir, entry.Name())
		if entry.IsDir() {
			err := addDirToZip(zipWriter, srcPath, arcname)
			if err != nil {
				return err
			}
		} else {
			err := addFileToZip(zipWriter, srcPath, arcname)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// addFileToZip 将单个文件添加到ZIP存档中。
// 参数:
//
// - zipWriter - 指向zip.Writer的指针，用于管理ZIP存档的写入。
// - srcPath - 字符串类型，表示源文件的路径。
// - arcname - 字符串类型，表示文件在ZIP存档中的名称。
//
// 返回值:
//
// - error - 如果在添加文件到ZIP存档的过程中遇到任何错误，将返回该错误。
func addFileToZip(zipWriter *zip.Writer, srcPath string, arcname string) error {
	file, openErr := os.Open(srcPath)
	if openErr != nil {
		return fmt.Errorf(ErrPathOpenMSG, srcPath, openErr)
	}
	defer file.Close()
	fileInfo, statErr := file.Stat()
	if statErr != nil {
		return fmt.Errorf(ErrPathStatMSG, srcPath, statErr)
	}
	relPath, err := filepath.Rel(arcname, srcPath)
	if err != nil {
		return fmt.Errorf(ErrPathRelativeMsg, arcname, srcPath, err)
	}
	header, err := zip.FileInfoHeader(fileInfo)
	if err != nil {
		return fmt.Errorf(ErrPathCreateMSG, relPath, err)
	}
	header.Name = relPath
	header.Method = zip.Deflate
	write, headerErr := zipWriter.CreateHeader(header)
	if headerErr != nil {
		return fmt.Errorf(ErrPathCreateMSG, relPath, headerErr)
	}
	_, copyErr := io.Copy(write, file)
	if copyErr != nil {
		return fmt.Errorf(ErrPathCopyMSG, srcPath, arcname, copyErr)
	}
	return nil
}

// IsTargzFile 检查给定路径的文件是否为tar.gz格式的压缩文件。
// 参数:
//
//	path - 文件的路径，应为绝对路径。
//
// 返回值:
//
//	bool - 如果文件是tar.gz文件则为true，否则为false。
//	error - 如果检查文件的过程发生任何错误，则返回该错误。
func IsTargzFile(path string) (bool, error) {
	if !filepath.IsAbs(path) {
		return false, fmt.Errorf(ErrPathNotAbsMSG, path)
	}
	if !strings.HasSuffix(path, ".tar.gz") {
		return false, fmt.Errorf(ErrFileTypeMSG, "tar", path)
	}
	return true, nil
}

// UnTargzPath 解压指定的tar.gz文件到目标路径。
// 参数:
//
//	srcPath - 源文件或目录的路径，必须是绝对路径。
//	dstPath - 目标文件或目录的路径，必须是绝对路径。
//	isMkdir - 是否在解压前创建目标路径的目录。
//
// 返回值:
//
//	error - 如果解压tar.gz文件的过程发生任何错误，则返回该错误。
func UnTargzPath(srcPath string, dstPath string, isMkdir bool) (string, error) {
	if !filepath.IsAbs(srcPath) {
		return "", fmt.Errorf(ErrPathNotAbsMSG, srcPath)
	}
	if !filepath.IsAbs(dstPath) {
		return "", fmt.Errorf(ErrPathNotAbsMSG, dstPath)
	}
	isTar, checkTargzErr := IsTargzFile(srcPath)
	if checkTargzErr != nil {
		return "", checkTargzErr
	} else if !isTar {
		return "", fmt.Errorf(ErrFileTypeMSG, "tar", srcPath)
	}
	if isMkdir {
		mkdirErr := MakrPathDir(dstPath, false)
		if mkdirErr != nil {
			return "", mkdirErr
		}
	}
	srcFile, openErr := os.Open(srcPath)
	if openErr != nil {
		return "", fmt.Errorf(ErrPathOpenMSG, srcPath, openErr)
	}
	defer srcFile.Close()

	gzr, gzipErr := gzip.NewReader(srcFile)
	if gzipErr != nil {
		return "", fmt.Errorf("gzip decompressor failed to create the file, path=%s, err=%w", srcPath, gzipErr)
	}
	defer gzr.Close()

	tr := tar.NewReader(gzr)
	for {
		header, tarErr := tr.Next()
		if tarErr == io.EOF {
			break
		}
		if tarErr != nil {
			return "", fmt.Errorf(ErrPathReadMSG, srcPath, tarErr)
		}
		targetPath := filepath.Join(dstPath, header.Name)
		switch header.Typeflag {
		case tar.TypeDir:
			if err := os.MkdirAll(targetPath, os.FileMode(header.Mode)); err != nil {
				return "", fmt.Errorf(ErrPathCreateMSG, targetPath, err)
			}
		case tar.TypeReg:
			dstFile, err := os.OpenFile(targetPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.FileMode(header.Mode))
			if err != nil {
				return "", fmt.Errorf(ErrPathOpenMSG, targetPath, err)
			}
			defer dstFile.Close()

			if _, err := io.Copy(dstFile, tr); err != nil {
				return "", fmt.Errorf(ErrPathCopyMSG, srcPath, dstPath, err)
			}
		default:
			return "", fmt.Errorf("unsupported item types: %c", header.Typeflag)
		}
	}
	return "", nil
}

// TargzPath 将指定路径下的文件或目录打包成tar.gz格式的文件。
// 参数:
//
//	srcPath - 要打包的文件或目录的路径，必须是绝对路径。
//	dstPath - 目标文件路径，必须是绝对路径。
//
// 返回值:
//
//	error - 如果打包过程发生任何错误，则返回该错误。
func TargzPath(srcPath string, dstPath string) error {
	if !filepath.IsAbs(srcPath) {
		return fmt.Errorf(ErrPathNotAbsMSG, srcPath)
	}
	if !filepath.IsAbs(dstPath) {
		return fmt.Errorf(ErrPathNotAbsMSG, dstPath)
	}
	srcInfo, srcStatErr := os.Stat(srcPath)
	if srcStatErr != nil {
		if os.IsNotExist(srcStatErr) {
			return fmt.Errorf(ErrPathNotExistMSG, srcPath)
		}
		return fmt.Errorf(ErrPathStatMSG, srcPath, srcStatErr)
	}
	if !strings.HasSuffix(dstPath, ".tar.gz") {
		return fmt.Errorf(ErrFileTypeMSG, "tar", dstPath)
	}
	dstDir := filepath.Dir(dstPath)
	mkdirErr := MakrPathDir(dstDir, false)
	if mkdirErr != nil {
		return mkdirErr
	}
	dstFile, createErr := os.Create(dstPath)
	if createErr != nil {
		return fmt.Errorf(ErrPathCreateMSG, dstPath, createErr)
	}
	defer dstFile.Close()

	gzw := gzip.NewWriter(dstFile)
	defer gzw.Close()

	tw := tar.NewWriter(gzw)
	defer tw.Close()
	if srcInfo.IsDir() {
		return addDirToTargz(tw, srcPath, filepath.Dir(srcPath))
	}
	return addFileToTargz(tw, srcPath, filepath.Dir(srcPath))
}

// addDirToTargz 递归添加目录下的所有文件和子目录到tar.gz文件中。
// 参数:
//
//	tw - tar.Writer 指针，用于写入 tar.gz 文件。
//	srcDir (string) - 需要添加到 tar.gz 文件的源目录路径。
//	arcname (string) - 文件在 tar.gz 文件中的存档名称。
//
// 返回值:
//
//	error - 如果操作成功，则返回 nil；否则返回错误。
func addDirToTargz(tw *tar.Writer, srcDir string, arcname string) error {
	entries, readErr := os.ReadDir(srcDir)
	if readErr != nil {
		return fmt.Errorf(ErrPathReadMSG, srcDir, readErr)
	}

	for _, entry := range entries {
		srcPath := filepath.Join(srcDir, entry.Name())
		if entry.IsDir() {
			err := addDirToTargz(tw, srcPath, arcname)
			if err != nil {
				return err
			}
		} else {
			err := addFileToTargz(tw, srcPath, arcname)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// addFileToTargz 将单个文件添加到ZIP存档中。
// 参数:
//
// - tw - tar.Writer 指针，用于写入 tar.gz 文件。
// - srcPath - 字符串类型，表示源文件的路径。
// - arcname - 字符串类型，表示文件在.tar.gz存档中的名称。
//
// 返回值:
//
// - error - 如果在添加文件到ZIP存档的过程中遇到任何错误，将返回该错误。
func addFileToTargz(tw *tar.Writer, srcPath string, arcname string) error {
	file, openErr := os.Open(srcPath)
	if openErr != nil {
		return fmt.Errorf(ErrPathOpenMSG, srcPath, openErr)
	}
	defer file.Close()
	fileInfo, statErr := file.Stat()
	if statErr != nil {
		return fmt.Errorf(ErrPathStatMSG, srcPath, statErr)
	}
	relPath, err := filepath.Rel(arcname, srcPath)
	if err != nil {
		return fmt.Errorf(ErrPathRelativeMsg, arcname, srcPath, err)
	}
	header, err := tar.FileInfoHeader(fileInfo, relPath)
	if err != nil {
		return fmt.Errorf(ErrPathCreateMSG, arcname, err)
	}
	header.Name = relPath
	if err := tw.WriteHeader(header); err != nil {
		return fmt.Errorf(ErrPathWriteMSG, arcname, err)
	}
	if _, copyErr := io.Copy(tw, file); copyErr != nil {
		return fmt.Errorf(ErrPathCopyMSG, srcPath, arcname, copyErr)
	}
	return nil
}
